package com.vanke.agent.tools.merge;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

import com.vanke.agent.ADataSource;
import com.vanke.agent.C;
import com.vanke.agent.bean.CstInfo;
import com.vanke.agent.common.CstUtil;
import com.vanke.agent.common.Util;
import com.vanke.agent.common.log.LoggerUtil;
import com.vanke.agent.sync.AgentContext;

/**
 */
public class Merge {
	public static void main(String[] args) {
		Merge merge = new Merge();
		merge.mergeCst(Long.valueOf(args[0]), Long.valueOf(args[1]));
		// merge.mergeCst(145000012, 145000008);
	}

	private Logger bizLog = LoggerUtil.getLoggerByName("merge");
	private Logger appLog = Logger.getLogger(Merge.class);
	private Set<String> allTables = new HashSet<String>();

	public boolean mergeCst(long from, long to) {
		Connection bizCon = ADataSource.getConnection(ADataSource.SCHEMA_BIZ);
		Statement stmt = null;
		try {
			bizCon.setAutoCommit(false);
			CstInfo cstFrom = CstUtil.getByGuid(bizCon, from);
			CstInfo cstTo = CstUtil.getByGuid(bizCon, to);
			bizLog.info("把[" + (cstFrom == null ? "null" : cstFrom.toString())
					+ "]\n合并到[" + (cstTo == null ? "null" : cstTo.toString())
					+ "]");
			bizLog.info(C.SPLIT_LINE);
			if (cstFrom == null) {
				bizLog.info("无法执行合并");
				return false;
			}
			if (cstTo == null) {
				bizLog.info("无法执行合并");
				return false;
			}

			MergeBackup backup = new MergeBackup();
			loadCache(bizCon, from, to); 	// 加载映射
			updateMappingList();			// 更新映射列表
			backup.backup(cstFrom, cstTo, 	// 备份
					cstMobileMap,			//
					cstCardIdMap, 			//
					cstOriGuidMap);			//

			stmt = bizCon.createStatement();
			mergeCstInfo(stmt, from, to); 		// 主档合并
			retainInfo(stmt, cstFrom, cstTo);	// 保留非空信息
			mergeMapping(stmt, cstFrom, cstTo); // 合并映射
			mergeRecommed(stmt, from, to);		// 合并推荐关系
			mergeProductOrder(stmt, from, to); 	// 合并订购关系
			mergeFollowRecord(stmt, from, to); 	// 合并跟进记录
			mergeMemberInfo(stmt, from, to); 	// 合并会员信息
			mergeCstAccount(stmt, from, to);	// 合并联名ID

			bizLog.info("执行合并");
			stmt.executeBatch();
			bizLog.info("提交合并");
			bizCon.commit();
			bizLog.info("合并成功！");

			updateMergedMapping(from, to);
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			bizLog.error("合并异常：" + e);
			try {
				bizCon.rollback();
			} catch (SQLException e1) {
				e1.printStackTrace();
			}
			return false;
		} finally {
			ADataSource.close(null, stmt, bizCon);
			releaseCache();
		}
	} //~mergeCustomerInfo

	// 对换指定时间段内的跟进记录
	protected void mergeCstInfo(Statement stmt, long from, long to)
			throws SQLException {
		// 跟进记录表
		String fromTable = CstUtil.calcCstSplitTable("cst_Customer_", from);
		String toTable = CstUtil.calcCstSplitTable("cst_Customer_", to);
		if (fromTable.equals(toTable)) { // 在同一表中
			bizLog.info(String.format("合并主档[%s]->[%s]，在同一表中", from, to));
			String fmt = "delete from %s where CstGUID=%s";
			String sql;
			// delete to
			sql = String.format(fmt, toTable, to);
			stmt.addBatch(sql);
			bizLog.info(sql);

			fmt = "update %s set CstGUID=%s where CstGUID=%s";
			// from -> to
			sql = String.format(fmt, fromTable, to, from);
			stmt.addBatch(sql);
			bizLog.info(sql);
			return;
		} // end if
			// fromTable -> toTable
		bizLog.info(String.format("合并主档[%s]->[%s]，在不同表中", from, to));

		String fmt = "delete from %s where CstGUID=%s";
		String sql;
		// delete to
		sql = String.format(fmt, toTable, to);
		stmt.addBatch(sql);
		bizLog.info(sql);

		fmt = "insert into %s (select * from %s where CstGUID=%s)";
		// from -> to
		sql = String.format(fmt, toTable, fromTable, from);
		stmt.addBatch(sql);
		bizLog.info(sql);

		fmt = "delete from %s where CstGUID=%s";
		// delete from
		sql = String.format(fmt, fromTable, from);
		stmt.addBatch(sql);
		bizLog.info(sql);

		fmt = "update %s set CstGUID=%s where CstGUID=%s";
		// update to
		sql = String.format(fmt, toTable, to, from);
		stmt.addBatch(sql);
		bizLog.info(sql);
	} //~mergeCstInfo(Statement stmt, long from, long to)

	/* 全并保留，非空信息 */
	protected void retainInfo(Statement stmt, CstInfo cstFrom, CstInfo cstTo)
			throws SQLException {
		String table;
		table = CstUtil.calcCstSplitTable("cst_Customer_", cstTo.getCstGUID());
		String sql = "update " + table + " set CstSrc='my' ";
		// 姓名
		sql += genAppendUpdate("CstName", cstFrom.getCstName(),
				cstTo.getCstName());
		// 身份证
		sql += genAppendUpdate("CardID", cstFrom.getCardId(), cstTo.getCardId());
		// 手机
		sql += genAppendUpdate("MobileTel", cstFrom.getMobile(),
				cstTo.getMobile());
		// 办公电话
		sql += genAppendUpdate("OfficeTel", cstFrom.getOfficeTel(),
				cstTo.getOfficeTel());
		// 家庭电话
		sql += genAppendUpdate("HomeTel", cstFrom.getHomeTel(),
				cstTo.getHomeTel());
		// 住址
		sql += genAppendUpdate("Address", cstFrom.getAddress(),
				cstTo.getAddress());
		sql += " where CstGUID=" + cstTo.getCstGUID();
		bizLog.info(sql);
		stmt.addBatch(sql);
	} //~retainInfo(CstInfo cstFrom, CstInfo cstTo)

	/*
	 * 取非空字段
	 */
	protected String genAppendUpdate(String field, String retain, String to) {
		if (retain == null || retain.trim().length() == 0) {
			retain = (to == null ? "" : to.trim());
		}
		retain = retain.trim();
		if (retain.length() == 0) {
			return "";
		}
		return "," + field + "='" + retain.replaceAll("'", "") + "'";
	} //~ensure(String retain, String to)

	/* 映射关系合并 */
	protected void mergeMapping(Statement stmt, CstInfo cstFrom, CstInfo cstTo)
			throws SQLException {
		long from = cstFrom.getCstGUID();
		long to = cstTo.getCstGUID();

		Set<String> cardids = getCardIds(from);
		for (String cardid : cardids) {
			bizLog.info(String.format("合并身份证映射[%s-%s]->[%s-%s]", cardid, from,
					cardid, to));
			String suffix = cardid.toUpperCase().substring(cardid.length() - 2);
			String sql = "update cst_CstCardIdMapping_" + suffix
					+ " set CstGUID=" + to + " where cardId='" + cardid + "'";
			bizLog.info(sql);
			stmt.addBatch(sql);
		}
		Set<String> oriGUIDs = getOriGUIDs(from);
		for (String origuid : oriGUIDs) {
			bizLog.info(String.format("合并明源GUID映射[%s-%s]->[%s-%s]", origuid,
					from, origuid, to));
			String suffix = origuid.toUpperCase().substring(
					origuid.length() - 1);
			String sql = "update cst_CstGUIDMapping_" + suffix
					+ " set CstGUID=" + to + " where OriGUID='" + origuid + "'";
			bizLog.info(sql);
			stmt.addBatch(sql);
		}
		Set<String> mobiles = getCstMobiles(from);
		bizLog.info("合并手机映射列表" + mobiles);
		for (String mobile : mobiles) {
			bizLog.info(String.format("合并手机映射[%s-%s]->[%s-%s]", mobile, from,
					mobile, to));
			String suffix = mobile.substring(mobile.length() - 2);
			String mobileMappingTable = "cst_CstMobileMapping_" + suffix;
			String sql = "update " + mobileMappingTable + " set cstguid=" + to
					+ " where mobile='" + mobile + "'";
			bizLog.info(sql);
			stmt.addBatch(sql);
		}
	} //~

	//-------------------------------------------------------------------
	protected void updateMergedMapping(long from, long to) {
		appLog.info("更新合并后映射列表");
		Connection bizCon = ADataSource.getConnection(ADataSource.SCHEMA_BIZ);
		try {
			cstMobileMap.get(to).addAll(cstMobileMap.get(from));
			cstCardIdMap.get(to).addAll(cstCardIdMap.get(from));
			cstOriGuidMap.get(to).addAll(cstOriGuidMap.get(from));

			updateMapping(bizCon, to);
		} catch (Exception e) {
			e.printStackTrace();
			bizLog.error("[updateMergedMapping]更新合并后映射列表：" + e);
		} finally {
			ADataSource.close(bizCon);
		}
	}

	/*
	 * 更新映射列表
	 */
	protected void updateMappingList() {
		appLog.info("更新映射列表");
		Connection bizCon = ADataSource.getConnection(ADataSource.SCHEMA_BIZ);
		try {
			Iterator<Long> iter = cstOriGuidMap.keySet().iterator();
			while (iter.hasNext()) {
				long guid = iter.next();
				updateMapping(bizCon, guid);
				updateFollowRecordOriCstGUID(bizCon, guid);
			}
		} catch (Exception e) {
			e.printStackTrace();
			bizLog.error("[updateMappingList]更新映射列表异常：" + e);
		} finally {
			ADataSource.close(bizCon);
		}
	} //~updateOriGUIDList

	/*
	 * 更新跟进里的明源GUID
	 */
	protected void updateFollowRecordOriCstGUID(Connection con, long guid) {
		Set<String> oriGuids = cstOriGuidMap.get(guid);
		if (oriGuids.size() == 0) {
			return;
		}
		Statement stmt = null;
		try {
			String table = CstUtil.retrieveCstReceiveTable(guid);
			if (!allTables.contains(table)) {
				return;
			}
			stmt = con.createStatement();
			String sql = "update " + table + " set OriCstGUID='"
					+ oriGuids.iterator().next() + "' where CstGUID=" + guid
					+ " AND (OriCstGUID is null or OriCstGUID='')";
			bizLog.info("更新跟进记录[" + sql + "]");
			stmt.executeUpdate(sql);
		} catch (Exception e) {
			e.printStackTrace();
			bizLog.error("[updateMappingList]更新跟进记录OriCstGUIDs异常：" + e);
		} finally {
			ADataSource.close(stmt);
		}
	} //~updateFollowRecordOriCstGUID(Connection con, long guid)

	/*
	 * 更新指定客户映射列表（手机、身份证、明源GUID）
	 */
	private boolean updateMapping(Connection bizCon, long guid) {
		PreparedStatement pstmt = null;
		String table = CstUtil.retrieveCustomerTable(guid);
		String sql = "update " + table
				+ " set contact=?,CardIdList=?,OriGUIDList=? where CstGUID=?";
		try {
			Set<String> mobiles = cstMobileMap.get(guid);
			Set<String> cardIds = cstCardIdMap.get(guid);
			Set<String> oriGuids = cstOriGuidMap.get(guid);

			String fmt = "更新映射列表[%s]{mobiles=%s,cardIds=%s,oriGuids=%s}";
			bizLog.info(String.format(fmt, guid, mobiles, cardIds, oriGuids));

			pstmt = bizCon.prepareStatement(sql);
			pstmt.setString(1, Util.convertToString(mobiles));
			pstmt.setString(2, Util.convertToString(cardIds));
			pstmt.setString(3, Util.convertToString(oriGuids));
			pstmt.setLong(4, guid);
			pstmt.executeUpdate();
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			bizLog.error("[updateMapping]更新客户映射列表异常：" + e);
			return false;
		} finally {
			ADataSource.close(pstmt);
		}
	} //~updateMapping

	//-------------------------------------------------------------------
	protected Map<Long, Set<String>> cstMobileMap = new HashMap<Long, Set<String>>();
	protected Map<Long, Set<String>> cstOriGuidMap = new HashMap<Long, Set<String>>();
	protected Map<Long, Set<String>> cstCardIdMap = new HashMap<Long, Set<String>>();

	protected void loadCache(Connection bizCon, long from, long to) {
		cstMobileMap.put(from, new HashSet<String>());
		cstOriGuidMap.put(from, new HashSet<String>());
		cstCardIdMap.put(from, new HashSet<String>());
		cstMobileMap.put(to, new HashSet<String>());
		cstOriGuidMap.put(to, new HashSet<String>());
		cstCardIdMap.put(to, new HashSet<String>());

		appLog.info("MERGE加载所有手机映射、身份证映射、明源GUID映射，请稍候...");
		{
			List<String> tables = CstUtil.loadTables(bizCon,
					"cst_cstmobilemapping_");
			int count = 0;
			long now = System.currentTimeMillis();
			for (String table : tables) {
				count += loadCstMobileMapping(bizCon, table, from, to);
			}
			now = System.currentTimeMillis() - now;
			appLog.info(count + " mobile mapping cached, spend " + now + "ms");
		}
		{
			List<String> tables = CstUtil.loadTables(bizCon,
					"cst_cstguidmapping_");
			int count = 0;
			long now = System.currentTimeMillis();
			for (String table : tables) {
				loadOriGUIDMapping(bizCon, table, from, to);
			}
			now = System.currentTimeMillis() - now;
			appLog.info(count + " guid mapping cached, spend " + now + "ms");
		}
		{
			List<String> tables = CstUtil.loadTables(bizCon,
					"cst_cstcardidmapping_");
			int count = 0;
			long now = System.currentTimeMillis();
			for (String table : tables) {
				loadCardIdMapping(bizCon, table, from, to);
			}
			now = System.currentTimeMillis() - now;
			appLog.info(count + " mobile cardid cached, spend " + now + "ms");
		}

		Statement stmt = null;
		ResultSet rs = null;
		try {
			stmt = bizCon.createStatement();
			rs = stmt.executeQuery("show tables");
			while (rs.next()) {
				allTables.add(rs.getString(1));
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			ADataSource.close(rs, stmt, null);
		}
	} //~loadCache

	protected void releaseCache() {
		cstMobileMap.clear();
		cstOriGuidMap.clear();
		cstCardIdMap.clear();
	}

	protected int loadCstMobileMapping(Connection bizCon, String table,
			long from, long to) {
		Statement stmt = null;
		ResultSet rs = null;
		int count = 0;
		try {
			stmt = bizCon.createStatement();
			String sql = "select mobile,CstGUID from " + table
					+ " where CstGUID=" + from + " or CstGUID=" + to;
			rs = stmt.executeQuery(sql);
			while (rs.next()) {
				count++;
				String mobile = rs.getString(1);
				long guid = rs.getLong(2);
				Set<String> set = cstMobileMap.get(guid);
				if (set == null) {
					continue;
				}
				set.add(mobile);
			}
		} catch (Exception e) {
			e.printStackTrace();
			AgentContext.exit("加载手机映射缓存失败，无法继续同步；否则将会产生不可预知的垃圾数据");
		} finally {
			ADataSource.close(rs, stmt, null);
		}
		return count;
	} //~

	protected int loadOriGUIDMapping(Connection bizCon, String table,
			long from, long to) {
		Statement stmt = null;
		ResultSet rs = null;
		int count = 0;

		try {
			stmt = bizCon.createStatement();
			String sql = "select OriGUID,CstGUID from " + table
					+ " where CstGUID=" + from + " or CstGUID=" + to;
			rs = stmt.executeQuery(sql);
			while (rs.next()) {
				count++;
				String origuid = rs.getString(1);
				long guid = rs.getLong(2);
				Set<String> set = cstOriGuidMap.get(guid);
				if (set == null) {
					continue;
				}
				set.add(origuid);
			}
		} catch (Exception e) {
			e.printStackTrace();
			AgentContext.exit("加载GUID映射缓存失败，无法继续同步；否则将会产生不可预知的垃圾数据");
		} finally {
			ADataSource.close(rs, stmt, null);
		}
		return count;
	} //~

	protected int loadCardIdMapping(Connection bizCon, String table, long from,
			long to) {
		Statement stmt = null;
		ResultSet rs = null;
		int count = 0;
		try {
			stmt = bizCon.createStatement();
			String sql = "select cardid,CstGUID from " + table
					+ " where CstGUID=" + from + " or CstGUID=" + to;
			rs = stmt.executeQuery(sql);
			while (rs.next()) {
				String cardid = rs.getString(1);
				long guid = rs.getLong(2);
				Set<String> set = cstCardIdMap.get(guid);
				if (set == null) {
					continue;
				}
				count++;
				set.add(cardid);
			}
		} catch (Exception e) {
			e.printStackTrace();
			AgentContext.exit("加载身份证映射缓存失败，无法继续同步；否则将会产生不可预知的垃圾数据");
		} finally {
			ADataSource.close(rs, stmt, null);
		}
		return count;
	} //~

	public Set<String> getCstMobiles(long guid) {
		Set<String> set = cstMobileMap.get(guid);
		return set == null ? new HashSet<String>() : set;
	}

	protected Set<String> getOriGUIDs(long guid) {
		Set<String> set = cstOriGuidMap.get(guid);
		return set == null ? new HashSet<String>() : set;
	}

	protected Set<String> getCardIds(long guid) {
		Set<String> set = cstCardIdMap.get(guid);
		return set == null ? new HashSet<String>() : set;
	}

	//-------------------------------------------------------------------
	// 合并推荐关系
	protected void mergeRecommed(Statement stmt, long from, long to)
			throws SQLException {
		// 跟进记录表
		String fromTable = CstUtil.calcCstSplitTable("prm_Agent_Cst_Recommed_",
				from);
		String toTable = CstUtil.calcCstSplitTable("prm_Agent_Cst_Recommed_",
				to);

		if (fromTable.equals(toTable)) { // 跟进记录在同一表中
			bizLog.info(String.format("合并推荐关系[%s]->[%s]，在同一表中", from, to));
			if (!allTables.contains(fromTable)) {
				bizLog.warn("表[" + fromTable + "]不存在");
				return;
			}
			String fmt = "update %s set CstGUID=%s where CstGUID=%s";
			String sql;
			// from -> to
			sql = String.format(fmt, fromTable, to, from);
			stmt.addBatch(sql);
			bizLog.info(sql);
			return;
		} //
			// 跟进记录在不同表中
			// fromTable -> toTable
		bizLog.info(String.format("合并推荐关系[%s]->[%s]，在不同表中", from, to));
		if (!allTables.contains(fromTable)) {
			bizLog.warn("表[" + fromTable + "]不存在");
			return;
		}
		if (!allTables.contains(toTable)) {
			bizLog.warn("表[" + toTable + "]不存在");
			return;
		}

		String fmt = "insert into %s(%s)(select %s from %s where CstGUID=%s)";
		String sql;
		// from -> to
		sql = String
				.format(fmt, toTable, RE_FIELDS, RE_FIELDS, fromTable, from);
		stmt.addBatch(sql);
		bizLog.info(sql);

		fmt = "delete from %s where CstGUID=%s";
		// delete from
		sql = String.format(fmt, fromTable, from);
		stmt.addBatch(sql);
		bizLog.info(sql);

		fmt = "update %s set CstGUID=%s where CstGUID=%s";
		// update to
		sql = String.format(fmt, toTable, to, from);
		stmt.addBatch(sql);
		bizLog.info(sql);
	} //~mergeRecommed

	//订购关系合并
	protected void mergeProductOrder(Statement stmt, long from, long to)
			throws SQLException {
		// 订购关系记录表
		String fromTable = CstUtil
				.calcCstSplitTable("cst_Product_Order_", from);
		String toTable = CstUtil.calcCstSplitTable("cst_Product_Order_", to);
		if (fromTable.equals(toTable)) { // 跟进记录在同一表中
			bizLog.info(String.format("合并订购关系[%s]->[%s]，在同一表中", from, to));
			if (!allTables.contains(fromTable)) {
				bizLog.warn("表[" + fromTable + "]不存在");
				return;
			}
			String fmt = "update %s set CstGUID=%s where CstGUID=%s ";
			// from -> to
			String sql = String.format(fmt, fromTable, to, from);
			stmt.addBatch(sql);
			bizLog.info(sql);
			return;
		} // end if

		bizLog.info(String.format("合并订购关系[%s]->[%s]，在不同表中", from, to));
		if (!allTables.contains(fromTable)) {
			bizLog.warn("表[" + fromTable + "]不存在");
			return;
		}
		if (!allTables.contains(toTable)) {
			bizLog.warn("表[" + toTable + "]不存在");
			return;
		}

		String fmt = "insert into %s(" + PO_FIELDS + ") (select " + PO_FIELDS
				+ " from %s where CstGUID=%s)";
		// from -> to
		String sql = String.format(fmt, toTable, fromTable, from);
		stmt.addBatch(sql);
		bizLog.info(sql);
		fmt = "delete from %s where CstGUID=%s ";

		// delete from
		sql = String.format(fmt, fromTable, from);
		stmt.addBatch(sql);
		bizLog.info(sql);

		fmt = "update %s set CstGUID=%s where CstGUID=%s ";
		// update to
		sql = String.format(fmt, toTable, to, from);
		stmt.addBatch(sql);
		bizLog.info(sql);
	} //~mergeProductOrder(Statement stmt, long from, long to)

	protected void mergeMemberInfo(Statement stmt, long from, long to)
			throws SQLException {
		// 跟进记录表
		String fromTable = CstUtil.calcCstSplitTable("cst_Member_", from);
		String toTable = CstUtil.calcCstSplitTable("cst_Member_", to);

		if (fromTable.equals(toTable)) {
			bizLog.info(String.format("合并会员关联[%s]->[%s]，在同一表中", from, to));
			if (!allTables.contains(fromTable)) {
				bizLog.warn("表[" + fromTable + "]不存在");
				return;
			}

			String fmt = "update %s set CstGUID=%s where CstGUID=%s";

			// from -> to
			String sql = String.format(fmt, fromTable, to, from);
			stmt.addBatch(sql);
			bizLog.info(sql);

			return;
		} // end if

		bizLog.info(String.format("更新会员关联[%s]->[%s]，在不同表中", from, to));
		if (!allTables.contains(fromTable)) {
			bizLog.warn("表[" + fromTable + "]不存在");
			return;
		}
		if (!allTables.contains(toTable)) {
			bizLog.warn("表[" + toTable + "]不存在");
			return;
		}

		// fromTable -> toTable
		String fmt = "insert into %s(" + MEM_FIELDS + ") (select " + MEM_FIELDS
				+ " from %s where CstGUID=%s)";

		// from -> to
		String sql = String.format(fmt, toTable, fromTable, from);
		stmt.addBatch(sql);
		bizLog.info(sql);

		fmt = "delete from %s where CstGUID=%s";
		// delete from
		sql = String.format(fmt, fromTable, from);
		stmt.addBatch(sql);
		bizLog.info(sql);

		fmt = "update %s set CstGUID=%s where CstGUID=%s";
		// update to
		sql = String.format(fmt, toTable, to, from);
		stmt.addBatch(sql);
		bizLog.info(sql);
	} //~mergeMemberInfo(Statement stmt, long from, long to)

	// 合并联名ID
	protected void mergeCstAccount(Statement stmt, long from, long to) 
			throws SQLException{
		String fmt = "update cst_Account set RealAccount=%s where CstGUID=%s";
		String sql = String.format(fmt, to, from);
		stmt.addBatch(sql);
		bizLog.info(sql);
	} //~mergeCstAccount(Statement stmt, long from, long to)
	
	// 对换指定时间段内的跟进记录
	protected void mergeFollowRecord(Statement stmt, long from, long to)
			throws SQLException {
		// 跟进记录表
		String fromTable = CstUtil.calcCstSplitTable("cst_FollowRecord_", from);
		String toTable = CstUtil.calcCstSplitTable("cst_FollowRecord_", to);

		if (fromTable.equals(toTable)) { // 跟进记录在同一表中
			bizLog.info(String.format("合并跟进记录[%s]->[%s]，在同一表中", from, to));
			if (!allTables.contains(fromTable)) {
				bizLog.warn("表[" + fromTable + "]不存在");
				return;
			}

			String fmt = "update %s set CstGUID=%s where CstGUID=%s";
			String sql;
			// from -> to
			sql = String.format(fmt, fromTable, to, from);
			stmt.addBatch(sql);
			bizLog.info(sql);
			return;
		}
		// 跟进记录在不同表中
		// fromTable -> toTable
		bizLog.info(String.format("合并跟进记录[%s]->[%s]，在不同表中", from, to));
		if (!allTables.contains(fromTable)) {
			bizLog.warn("表[" + fromTable + "]不存在");
			return;
		}
		if (!allTables.contains(toTable)) {
			bizLog.warn("表[" + toTable + "]不存在");
			return;
		}

		String fmt = "insert into %s(%s)(select %s from %s where CstGUID=%s)";
		String sql;
		// from -> to
		sql = String
				.format(fmt, toTable, FR_FIELDS, FR_FIELDS, fromTable, from);
		stmt.addBatch(sql);
		bizLog.info(sql);

		fmt = "delete from %s where CstGUID=%s";
		// delete from
		sql = String.format(fmt, fromTable, from);
		stmt.addBatch(sql);
		bizLog.info(sql);

		fmt = "update %s set CstGUID=%s where CstGUID=%s";
		// update to
		sql = String.format(fmt, toTable, to, from);
		stmt.addBatch(sql);
		bizLog.info(sql);
	} //~mergeFollowRecord(long from, long to, Date start, Date end)

	static String FR_FIELDS = "OppGUID,CstGUID,Description,ReceiveType,ReceiveDate,UserGUID,MyGUID,OriCstGUID,BuGUID,CreatonDate,pk_zzcx_id,ProjGUID,followType,cstType,`status`,modifyTime,appointType,ProjCode"; //,SrcType
	static String MEM_FIELDS = "OriMemGUID,CstGUID,OriCstGUID,BuGUID,MemCode,Logpwd,MemStation,SpecialType,JoinPath,JoinDate,IsSendJoinNote,LjPoint,ApplyDate,Shr,shDate,CardStatus,MakeCardReason,PostCardDate,Tjr,TjrMemCode,TjrRelation,TjRemark,ProjName,XckID,XckPwd,Saler,GetMemLevelWay,HoldLevelBeginDate,HoldLevelEndDate,oldMemCode,MakeCardDate,MemLevel,MaxMemLevel,MemStatus,StatusDate,StatusReason,StatusHr,OldStatus,TelList";
	static String PO_FIELDS = "CstGUID,CstName,ProductType,ProductCode,Status,OrderChangeTime,CreateTime,Comment,StartTime,EndTime,Count,Price,TotalMoney,Discount,OriCstGUID";
	static String RE_FIELDS = "ProjCode,RecommTime,CstGUID,AgentGUID,Comment,AppointTime,ConsultantId,productType,proxyCompanyId,prizeRule,isAccompany,enable,status,createTime,modifyTime,SrcType,CstName,CstMobile";
}
